Skip to content

feat: add Jungian Intelligence Layer service v3.2.0#5

Open
ckaraca wants to merge 1 commit intomainfrom
feature/jungian-intelligence-layer
Open

feat: add Jungian Intelligence Layer service v3.2.0#5
ckaraca wants to merge 1 commit intomainfrom
feature/jungian-intelligence-layer

Conversation

@ckaraca
Copy link
Contributor

@ckaraca ckaraca commented Mar 2, 2026

Summary

  • Adds JungianService and AsyncJungianService exposed as client.jungian.*
  • Methods: search_with_context(), get_individuation_score(), get_individuation_history(), get_synchronicity_patterns(), get_dream_history()

Test plan

  • Verify both sync and async clients expose jungian service
  • Test all 5 methods call correct API endpoints
  • Run type checking with mypy

Summary by Sourcery

Add synchronous and asynchronous Jungian Intelligence Layer services to the client, exposing archetype-aware memory search, individuation scoring, synchronicity patterns, and dream history APIs.

New Features:

  • Introduce JungianService and AsyncJungianService for accessing Jungian Intelligence Layer endpoints from sync and async clients.
  • Expose individuation scoring, individuation history, synchronicity patterns, dream consolidation history, and archetype-aware pattern search methods via the client.jungian interface.

Enhancements:

  • Define typed models for Jungian-related responses, including individuation metrics, archetyped patterns, synchronicity patterns, and dream consolidation records, and export them at the top-level package API.

Add JungianService and AsyncJungianService with 5 methods each:
search_with_context, get_individuation_score,
get_individuation_history, get_synchronicity_patterns,
get_dream_history. Includes 7 new Pydantic models for
archetypes, individuation, synchronicity, and dream data.
@sourcery-ai
Copy link

sourcery-ai bot commented Mar 2, 2026

Reviewer's Guide

Adds a synchronous and asynchronous Jungian Intelligence Layer service (client.jungian.*) along with typed models for individuation, archetype search, synchronicity patterns, and dream history, wiring them into the public SDK surface and HTTP client endpoints.

Sequence diagram for JungianService search_with_context and individuation calls

sequenceDiagram
    actor Developer
    participant PluggedInClient
    participant JungianService
    participant HTTP_API as HTTP_API_Server

    Developer->>PluggedInClient: create PluggedInClient(api_key)
    PluggedInClient->>JungianService: JungianService(self)
    activate JungianService
    JungianService-->>PluggedInClient: instance with client.jungian
    deactivate JungianService

    Developer->>JungianService: search_with_context(query, tool_name, outcome)
    activate JungianService
    JungianService->>PluggedInClient: request("POST", "/api/memory/archetype/inject", json=payload)
    activate PluggedInClient
    activate HTTP_API
    PluggedInClient->>HTTP_API: POST /api/memory/archetype/inject
    HTTP_API-->>PluggedInClient: 200 OK, JSON patterns
    deactivate HTTP_API
    PluggedInClient-->>JungianService: Response
    deactivate PluggedInClient
    JungianService-->>Developer: ArchetypeSearchResponse
    deactivate JungianService

    Developer->>JungianService: get_individuation_score()
    activate JungianService
    JungianService->>PluggedInClient: request("GET", "/api/memory/individuation")
    activate PluggedInClient
    activate HTTP_API
    PluggedInClient->>HTTP_API: GET /api/memory/individuation
    HTTP_API-->>PluggedInClient: 200 OK, JSON individuation
    deactivate HTTP_API
    PluggedInClient-->>JungianService: Response
    deactivate PluggedInClient
    JungianService-->>Developer: IndividuationResponse
    deactivate JungianService
Loading

Class diagram for Jungian Intelligence Layer services and models

classDiagram
    class PluggedInClient {
        +ClipboardService clipboard
        +DocumentService documents
        +JungianService jungian
        +RagService rag
        +UploadService uploads
        +AgentService agents
        +request(method, path, params, json)
    }

    class AsyncPluggedInClient {
        +AsyncClipboardService clipboard
        +AsyncDocumentService documents
        +AsyncJungianService jungian
        +AsyncRagService rag
        +AsyncUploadService uploads
        +AsyncAgentService agents
        +request(method, path, params, json)
    }

    class JungianService {
        -PluggedInClient client
        +JungianService(client)
        +search_with_context(query, tool_name, outcome) ArchetypeSearchResponse
        +get_individuation_score() IndividuationResponse
        +get_individuation_history(days) List~IndividuationHistoryEntry~
        +get_synchronicity_patterns() List~SynchronicityPattern~
        +get_dream_history() List~DreamConsolidation~
    }

    class AsyncJungianService {
        -AsyncPluggedInClient client
        +AsyncJungianService(client)
        +search_with_context(query, tool_name, outcome) ArchetypeSearchResponse
        +get_individuation_score() IndividuationResponse
        +get_individuation_history(days) List~IndividuationHistoryEntry~
        +get_synchronicity_patterns() List~SynchronicityPattern~
        +get_dream_history() List~DreamConsolidation~
    }

    class IndividuationComponents {
        +int memory_depth
        +int learning_velocity
        +int collective_contribution
        +int self_awareness
    }

    class IndividuationResponse {
        +int total
        +str level
        +str weekly_trend
        +str tip
        +IndividuationComponents components
    }

    class IndividuationHistoryEntry {
        +str date
        +int total
        +str level
        +IndividuationComponents components
    }

    class ArchetypedPattern {
        +str uuid
        +str archetype
        +str archetype_label
        +float archetype_weight
        +str pattern_type
        +str description
        +str pattern
        +float confidence
        +float similarity
    }

    class ArchetypeSearchResponse {
        +List~ArchetypedPattern~ patterns
    }

    class SynchronicityPattern {
        +str uuid
        +str pattern_type
        +str description
        +float confidence
        +int unique_profiles
    }

    class DreamConsolidation {
        +str uuid
        +int source_count
        +int token_savings
        +float cluster_similarity
        +str created_at
    }

    PluggedInClient --> JungianService : owns
    AsyncPluggedInClient --> AsyncJungianService : owns

    IndividuationResponse --> IndividuationComponents : has
    IndividuationHistoryEntry --> IndividuationComponents : has
    ArchetypeSearchResponse --> ArchetypedPattern : contains

    JungianService --> IndividuationResponse : returns
    JungianService --> IndividuationHistoryEntry : returns
    JungianService --> ArchetypeSearchResponse : returns
    JungianService --> SynchronicityPattern : returns
    JungianService --> DreamConsolidation : returns

    AsyncJungianService --> IndividuationResponse : returns
    AsyncJungianService --> IndividuationHistoryEntry : returns
    AsyncJungianService --> ArchetypeSearchResponse : returns
    AsyncJungianService --> SynchronicityPattern : returns
    AsyncJungianService --> DreamConsolidation : returns
Loading

File-Level Changes

Change Details Files
Introduce typed Pydantic models for Jungian Intelligence Layer responses and expose them in the public package interface.
  • Define IndividuationComponents, IndividuationResponse, IndividuationHistoryEntry, ArchetypedPattern, ArchetypeSearchResponse, SynchronicityPattern, and DreamConsolidation models with appropriate field aliases and Config.populate_by_name.
  • Export all Jungian-related types and services from the package init so they are available as top-level imports.
src/pluggedinkit/types.py
src/pluggedinkit/__init__.py
Add synchronous and asynchronous Jungian services and attach them to the main clients as client.jungian.
  • Create JungianService and AsyncJungianService with methods for archetype-aware search, individuation score and history retrieval, synchronicity pattern lookup, and dream history retrieval, including shared parsing helpers.
  • Wire JungianService and AsyncJungianService into PluggedInClient and AsyncPluggedInClient initializers as the jungian attribute.
  • Expose JungianService and AsyncJungianService from the services package init so they are part of the public SDK API.
src/pluggedinkit/services/jungian.py
src/pluggedinkit/client.py
src/pluggedinkit/services/__init__.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@gemini-code-assist
Copy link

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request integrates a new Jungian Intelligence Layer into the SDK, providing advanced capabilities for psychological development analysis, archetype-aware pattern recognition, and memory consolidation. This enhancement allows clients to interact with a sophisticated AI layer designed to understand and process information with a deeper, more nuanced context, significantly expanding the SDK's analytical and interpretive functionalities.

Highlights

  • New Jungian Intelligence Layer Service: Introduced JungianService and AsyncJungianService to provide access to Jungian-inspired intelligence features, accessible via client.jungian.*.
  • New API Methods: Added five new methods to the Jungian services: search_with_context(), get_individuation_score(), get_individuation_history(), get_synchronicity_patterns(), and get_dream_history().
  • New Data Models: Defined several new Pydantic models in types.py to represent data structures for the Jungian Intelligence Layer, including IndividuationComponents, ArchetypedPattern, and DreamConsolidation.
Changelog
  • src/pluggedinkit/init.py
    • Imported new Jungian Intelligence types such as IndividuationComponents, ArchetypedPattern, and DreamConsolidation.
    • Imported JungianService and AsyncJungianService.
    • Added all new Jungian types and services to the package's all export list.
  • src/pluggedinkit/client.py
    • Imported JungianService and AsyncJungianService.
    • Initialized JungianService as 'self.jungian' in the synchronous PluggedInClient.
    • Initialized AsyncJungianService as 'self.jungian' in the asynchronous AsyncPluggedInClient.
  • src/pluggedinkit/services/init.py
    • Imported AsyncJungianService and JungianService.
    • Added JungianService and AsyncJungianService to the module's all export list.
  • src/pluggedinkit/services/jungian.py
    • Created a new file defining the Jungian Intelligence Layer services.
    • Implemented shared helper functions for parsing API responses into typed models.
    • Defined the synchronous JungianService with methods for archetype search, individuation scoring, history, synchronicity patterns, and dream history.
    • Defined the asynchronous AsyncJungianService with corresponding async methods.
  • src/pluggedinkit/types.py
    • Added new Pydantic models for Jungian Intelligence Layer data structures, including IndividuationComponents, IndividuationResponse, IndividuationHistoryEntry, ArchetypedPattern, ArchetypeSearchResponse, SynchronicityPattern, and DreamConsolidation.
Activity
  • No activity (comments, reviews) has been recorded for this pull request yet.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 issue, and left some high level feedback:

  • The _parse_* helper functions currently return empty lists when the response shape is unexpected; consider raising a domain-specific error instead so API shape changes are surfaced clearly instead of failing silently.
  • The helper parsers (_parse_individuation_history, _parse_synchronicity_patterns, _parse_dream_history) are all typed as Any for the input data; tightening these to Union[Dict[str, Any], List[Dict[str, Any]]] (or similar) would make mypy and IDE support more accurate and catch misuse earlier.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The `_parse_*` helper functions currently return empty lists when the response shape is unexpected; consider raising a domain-specific error instead so API shape changes are surfaced clearly instead of failing silently.
- The helper parsers (`_parse_individuation_history`, `_parse_synchronicity_patterns`, `_parse_dream_history`) are all typed as `Any` for the input data; tightening these to `Union[Dict[str, Any], List[Dict[str, Any]]]` (or similar) would make mypy and IDE support more accurate and catch misuse earlier.

## Individual Comments

### Comment 1
<location path="src/pluggedinkit/services/jungian.py" line_range="41" />
<code_context>
+    return IndividuationResponse(**data)
+
+
+def _parse_individuation_history(data: Any) -> List[IndividuationHistoryEntry]:
+    """Parse the individuation history response into typed list."""
+    if isinstance(data, list):
</code_context>
<issue_to_address>
**issue (complexity):** Consider extracting shared list-extraction and request-building helpers to reduce repeated parsing and wiring logic across the Jungian service functions.

You can trim complexity a bit in two focused areas without changing any behavior.

### 1. Centralize the “flexible shape” parsing

The three helpers `_parse_individuation_history`, `_parse_synchronicity_patterns`, and `_parse_dream_history` all repeat the same pattern: accept `Any`, handle `list` or `dict` with a couple of possible keys, and silently fall back to `[]`.

You can keep the same accepted shapes but centralize the logic so each parser is simpler and less branched:

```python
from typing import Callable, Type, TypeVar

T = TypeVar("T")

def _extract_list_payload(
    data: Any,
    *,
    default_key: str,
    alt_key: Optional[str] = None,
) -> List[Dict[str, Any]]:
    if isinstance(data, list):
        return data
    if isinstance(data, dict):
        if default_key in data and isinstance(data[default_key], list):
            return data[default_key]
        if alt_key and alt_key in data and isinstance(data[alt_key], list):
            return data[alt_key]
    # Preserve current behavior: unexpected shape -> empty list
    return []
```

Then the individual parsers become much narrower and easier to reason about:

```python
def _parse_individuation_history(data: Any) -> List[IndividuationHistoryEntry]:
    entries = _extract_list_payload(data, default_key="history", alt_key="entries")
    return [IndividuationHistoryEntry(**entry) for entry in entries]

def _parse_synchronicity_patterns(data: Any) -> List[SynchronicityPattern]:
    patterns = _extract_list_payload(data, default_key="patterns")
    return [SynchronicityPattern(**p) for p in patterns]

def _parse_dream_history(data: Any) -> List[DreamConsolidation]:
    entries = _extract_list_payload(data, default_key="history", alt_key="consolidations")
    return [DreamConsolidation(**d) for d in entries]
```

If you want to make unexpected shapes more visible later, you can swap the `return []` in `_extract_list_payload` for a `PluggedInError` without changing all call sites.

---

### 2. Share sync/async request + parse wiring

The sync and async services mostly differ in:
- the client type, and
- whether `request` is awaited.

You can keep the separate public classes/signatures but factor out the “request + parse” pattern into a small helper to cut repetition. For example, for the individuation endpoints:

```python
# shared helper
def _build_individuation_history_params(days: int) -> Dict[str, str]:
    return {"history": "true", "days": str(days)}
```

Usage in sync:

```python
def get_individuation_history(self, days: int = 30) -> List[IndividuationHistoryEntry]:
    response = self.client.request(
        "GET",
        "/api/memory/individuation",
        params=_build_individuation_history_params(days),
    )
    return _parse_individuation_history(response.json())
```

Usage in async:

```python
async def get_individuation_history(self, days: int = 30) -> List[IndividuationHistoryEntry]:
    response = await self.client.request(
        "GET",
        "/api/memory/individuation",
        params=_build_individuation_history_params(days),
    )
    return _parse_individuation_history(response.json())
```

You can apply the same approach for:

- `_build_search_with_context_payload(query, tool_name, outcome)`
- constant paths like `"/api/memory/individuation"` / `"/api/memory/sync/patterns"` / `"/api/memory/dream/history"`

This keeps functionality identical while reducing duplicated literals and argument construction logic, and makes future changes (e.g., adding a param) less error-prone across sync/async variants.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

return IndividuationResponse(**data)


def _parse_individuation_history(data: Any) -> List[IndividuationHistoryEntry]:
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (complexity): Consider extracting shared list-extraction and request-building helpers to reduce repeated parsing and wiring logic across the Jungian service functions.

You can trim complexity a bit in two focused areas without changing any behavior.

1. Centralize the “flexible shape” parsing

The three helpers _parse_individuation_history, _parse_synchronicity_patterns, and _parse_dream_history all repeat the same pattern: accept Any, handle list or dict with a couple of possible keys, and silently fall back to [].

You can keep the same accepted shapes but centralize the logic so each parser is simpler and less branched:

from typing import Callable, Type, TypeVar

T = TypeVar("T")

def _extract_list_payload(
    data: Any,
    *,
    default_key: str,
    alt_key: Optional[str] = None,
) -> List[Dict[str, Any]]:
    if isinstance(data, list):
        return data
    if isinstance(data, dict):
        if default_key in data and isinstance(data[default_key], list):
            return data[default_key]
        if alt_key and alt_key in data and isinstance(data[alt_key], list):
            return data[alt_key]
    # Preserve current behavior: unexpected shape -> empty list
    return []

Then the individual parsers become much narrower and easier to reason about:

def _parse_individuation_history(data: Any) -> List[IndividuationHistoryEntry]:
    entries = _extract_list_payload(data, default_key="history", alt_key="entries")
    return [IndividuationHistoryEntry(**entry) for entry in entries]

def _parse_synchronicity_patterns(data: Any) -> List[SynchronicityPattern]:
    patterns = _extract_list_payload(data, default_key="patterns")
    return [SynchronicityPattern(**p) for p in patterns]

def _parse_dream_history(data: Any) -> List[DreamConsolidation]:
    entries = _extract_list_payload(data, default_key="history", alt_key="consolidations")
    return [DreamConsolidation(**d) for d in entries]

If you want to make unexpected shapes more visible later, you can swap the return [] in _extract_list_payload for a PluggedInError without changing all call sites.


2. Share sync/async request + parse wiring

The sync and async services mostly differ in:

  • the client type, and
  • whether request is awaited.

You can keep the separate public classes/signatures but factor out the “request + parse” pattern into a small helper to cut repetition. For example, for the individuation endpoints:

# shared helper
def _build_individuation_history_params(days: int) -> Dict[str, str]:
    return {"history": "true", "days": str(days)}

Usage in sync:

def get_individuation_history(self, days: int = 30) -> List[IndividuationHistoryEntry]:
    response = self.client.request(
        "GET",
        "/api/memory/individuation",
        params=_build_individuation_history_params(days),
    )
    return _parse_individuation_history(response.json())

Usage in async:

async def get_individuation_history(self, days: int = 30) -> List[IndividuationHistoryEntry]:
    response = await self.client.request(
        "GET",
        "/api/memory/individuation",
        params=_build_individuation_history_params(days),
    )
    return _parse_individuation_history(response.json())

You can apply the same approach for:

  • _build_search_with_context_payload(query, tool_name, outcome)
  • constant paths like "/api/memory/individuation" / "/api/memory/sync/patterns" / "/api/memory/dream/history"

This keeps functionality identical while reducing duplicated literals and argument construction logic, and makes future changes (e.g., adding a param) less error-prone across sync/async variants.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a new JungianService for both synchronous and asynchronous clients. The implementation is well-structured, adding new service methods, corresponding Pydantic types, and integrating them into the client and package exports.

My review includes a few suggestions for improvement:

  • In types.py, I've recommended updating the new Pydantic models to use model_config instead of the deprecated class Config for consistency with Pydantic v2.
  • In services/jungian.py, I've identified a robustness issue in the JSON parsing helper functions. They could fail if the API returns an unexpected data type. I've provided suggestions to make them more resilient.
  • I've also added a comment regarding the code duplication between the synchronous and asynchronous service classes, suggesting potential strategies to improve maintainability.

Comment on lines +43 to +49
if isinstance(data, list):
return [IndividuationHistoryEntry(**entry) for entry in data]
# If wrapped in an object, try the 'history' key
if isinstance(data, dict):
entries = data.get("history", data.get("entries", []))
return [IndividuationHistoryEntry(**entry) for entry in entries]
return []

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The current implementation is not fully robust. If the API returns an unexpected data type (e.g., None or a string instead of a list for history or entries), the function will raise a TypeError. It's safer to validate that the extracted data is a list before iterating over it and that each item is a dictionary before unpacking.

    items_raw = []
    if isinstance(data, list):
        items_raw = data
    elif isinstance(data, dict):
        items_raw = data.get("history")
        if not isinstance(items_raw, list):
            items_raw = data.get("entries")

    if not isinstance(items_raw, list):
        return []

    return [IndividuationHistoryEntry(**entry) for entry in items_raw if isinstance(entry, dict)]

Comment on lines +54 to +59
if isinstance(data, list):
return [SynchronicityPattern(**p) for p in data]
if isinstance(data, dict):
patterns = data.get("patterns", [])
return [SynchronicityPattern(**p) for p in patterns]
return []

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Similar to the other parsing functions in this file, this implementation is not robust against unexpected API response formats. If data.get('patterns') returns a non-list iterable or None, this function will raise a TypeError. The implementation should be updated to handle these cases gracefully.

Suggested change
if isinstance(data, list):
return [SynchronicityPattern(**p) for p in data]
if isinstance(data, dict):
patterns = data.get("patterns", [])
return [SynchronicityPattern(**p) for p in patterns]
return []
items_raw = []
if isinstance(data, list):
items_raw = data
elif isinstance(data, dict):
items_raw = data.get("patterns")
if not isinstance(items_raw, list):
return []
return [SynchronicityPattern(**p) for p in items_raw if isinstance(p, dict)]

Comment on lines +64 to +69
if isinstance(data, list):
return [DreamConsolidation(**d) for d in data]
if isinstance(data, dict):
entries = data.get("history", data.get("consolidations", []))
return [DreamConsolidation(**d) for d in entries]
return []

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

This function has the same robustness issue as the other parsers. It can fail with a TypeError if the API response for history or consolidations is not a list. It's important to validate the data type before iteration to prevent runtime errors.

Suggested change
if isinstance(data, list):
return [DreamConsolidation(**d) for d in data]
if isinstance(data, dict):
entries = data.get("history", data.get("consolidations", []))
return [DreamConsolidation(**d) for d in entries]
return []
items_raw = []
if isinstance(data, list):
items_raw = data
elif isinstance(data, dict):
items_raw = data.get("history")
if not isinstance(items_raw, list):
items_raw = data.get("consolidations")
if not isinstance(items_raw, list):
return []
return [DreamConsolidation(**d) for d in items_raw if isinstance(d, dict)]

# -----------------------------------------------------------------------------


class AsyncJungianService:

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The AsyncJungianService class is almost an exact copy of JungianService, with the only differences being async and await. This creates significant code duplication across all five methods, making future maintenance harder (e.g., adding a parameter requires changes in two places). While this pattern might be consistent with other services in the SDK, it's worth considering strategies to reduce this duplication for better maintainability. For example, a tool like unasync could generate the sync code from the async code, or parameter-building logic could be extracted into shared helper functions.

Comment on lines +402 to +403
class Config:
populate_by_name = True

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The project appears to be using Pydantic v2 (based on the use of @model_validator elsewhere). For new models, it's better to use model_config instead of the deprecated class Config for configuration. This improves consistency and future-proofs the code. This comment applies to all new Pydantic models in this file (IndividuationResponse, IndividuationHistoryEntry, ArchetypedPattern, SynchronicityPattern, DreamConsolidation).

    model_config = {"populate_by_name": True}

@claude
Copy link

claude bot commented Mar 2, 2026

Code Review -- feat: add Jungian Intelligence Layer service

Overall the PR follows the existing patterns well -- clean sync/async symmetry, proper use of TYPE_CHECKING, and thorough docstrings. Here are the issues found, from most to least critical.


Bugs

1. Unused import + missing API error checking (jungian.py:10)

PluggedInError is imported but never used in any method. Compare with clipboard.py which explicitly checks the success field before parsing:

If the API returns an error body, callers get a confusing Pydantic ValidationError instead of the documented PluggedInError. Either add a success check or remove the unused import -- the current state gives a false impression that errors are handled.

2. Possible camelCase mismatch in search_with_context payload (jungian.py:111)

The optional field is sent as snake_case: payload[tool_name] = tool_name. Every other JSON payload in this SDK uses camelCase (e.g. createdByTool, ttlSeconds). If the API expects toolName, this field is silently ignored, causing subtle context-injection failures. Please verify the expected API field name.


Type / Model Issues

3. DreamConsolidation.created_at and IndividuationHistoryEntry.date should be datetime (types.py:418, 468)

The rest of the models use datetime for timestamps (Document.created_at, ClipboardEntry.created_at). Using str is inconsistent and loses Pydantic's automatic ISO-8601 parsing.

4. No field constraints on score components (types.py:395-403)

IndividuationComponents fields have no bounds. The existing codebase shows the pattern (e.g. temperature: Optional[float] = Field(None, ge=0, le=2)). If scores have a fixed range (e.g. 0-100), add ge/le constraints.


Code Quality

**5. Prefer model_validate() over Model(data) for Pydantic v2 (jungian.py:32, 38)

Model(**data) works but Model.model_validate(data) is the idiomatic Pydantic v2 API. It handles nested coercion more explicitly and reliably.

6. _parse_archetype_search manual iteration is inconsistent (jungian.py:29-33)

The function manually iterates and constructs ArchetypedPattern instances, while _parse_individuation simply delegates to the model constructor. Pydantic already handles nested list coercion, so ArchetypeSearchResponse.model_validate(data) is sufficient and consistent.

7. No input validation on the days parameter (jungian.py:136, 252)

There is no guard against days <= 0. A simple check matches the defensive style used elsewhere in the SDK.


Other

8. Version not bumped in pyproject.toml

The PR title says v3.2.0 but pyproject.toml still reads version = 1.0.1. Adding a new public service warrants at least a minor version bump (1.1.0 under semver).

9. No tests

The test plan checkboxes are all unchecked and no tests/ directory exists, despite the project having pytest, pytest-asyncio, and pytest-cov as dev dependencies. Unit tests with mocked HTTP responses for each method (sync and async) would be a good addition before merging.


What is done well

  • Sync/async class structure exactly mirrors the existing service pattern.
  • TYPE_CHECKING guard prevents circular imports correctly.
  • Docstrings are thorough and match the existing format.
  • populate_by_name = True applied consistently across all new models.
  • The flexible parsing helpers (_parse_individuation_history, _parse_dream_history) gracefully handle both list and dict-wrapped API responses.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant